#ifndef __OBJLOADER__
#define __OBJLOADER__

#include <random>
#include <iostream>
#include <random>
#include <vector>
#include <algorithm>
#include <iostream>
#include <string>
#include <fstream>

#include <boost\tokenizer.hpp>
#include <time.h>

#include "Triangle.h"
#include <stdio.h>
#include "Quad.h"
#include "Material.h"
#include <map>
#include "RGBColor.h"
#include "Material.h"
#include "Matte.h"
#include "Logger.h"
//
//#include <boost\iostreams\code_converter.hpp>
//#include <boost\iostreams\device\mapped_file.hpp>

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
using Engine::Triangle;
using Engine::TriangleVertex;
using std::vector;
using Engine::Matte;

typedef boost::tokenizer<boost::char_separator<char>> tokeniz;

class ObjLoader
{
public:

	vector<GeometricObjects*> LoadFile(string path)
	{
		return ParseObjFile(path);
	}

	vector<GeometricObjects*> LoadFile(string path, Point3D centroid, bool p_useTextures = false)
	{
		return ParseObjFile(path, centroid, p_useTextures);
	}

	inline void ParseStringSentence(std::string toParse, std::string splittingString, std::vector<std::string>& output)
	{
		boost::char_separator<char> sep(splittingString.c_str());
		tokeniz tok(toParse, sep);


		int index = 0;
		for (tokeniz::iterator tokIter = tok.begin(); tokIter != tok.end(); ++tokIter)
		{
			output.push_back(tokIter.current_token().c_str());
		}
	}

	vector<std::string> ParseString(std::string toParse, std::string splittingString)
	{
		boost::char_separator<char> sep(splittingString.c_str());
		tokeniz tok(toParse, sep);
		vector<string> output;

		int index = 0;
		for (tokeniz::iterator tokIter = tok.begin(); tokIter != tok.end(); ++tokIter)
		{
			output.push_back(tokIter.current_token().c_str());
		}

		return output;
	}

	void ParseValues(std::string toParse, std::string splittingString, vector<string>& output)
	{
		boost::char_separator<char> sep(splittingString.c_str());
		tokeniz tok(toParse, sep);
		int amount = 0;
		for (tokeniz::iterator tokIter = tok.begin(); tokIter != tok.end(); ++tokIter)
		{
			amount++;
		}

		int index = 0;
		for (tokeniz::iterator tokIter = tok.begin(); tokIter != tok.end(); ++tokIter)
		{
			if (index == 0 | index > 1)
			{
				output.push_back(tokIter.current_token().c_str());
				if (amount == 1)
				{
					output.push_back("");
					output.push_back("");
				}
			}
			else
			{
				int size = output[0].size();
				char atSize = toParse.at(size);
				char atSize1 = toParse.at(size + 1);

				std::string check = std::string() + atSize + atSize1;

				if (std::strcmp(check.c_str(), "//") == 0)
				{
					output.push_back("");
					output.push_back(tokIter.current_token().c_str());
				}
				else if (amount == 2)
				{
					output.push_back(tokIter.current_token().c_str());
					output.push_back("");
				}
				else
				{
					output.push_back(tokIter.current_token().c_str());
				}

			}

			index++;
		}
	}

	void GetMaterials(string path, map<string, Engine::Material*>& allMaterials, bool containsTextures = false)
	{
		bool newfile = true;
		int index = 0;
		std::string detailsArray[2];
		bool firstTime = false;
		bool finishedFlag = false;
		//string fullPath = "C:\\Users\\Adrian\\Desktop\\" + path;
		//string fullPath = "\ObjFiles\\"+path;
		std::ifstream newFile(path);
		string line;
		if (newFile.is_open())
		{
			try
			{
				vector<string> tokens;
				string materialName = "";
				RGBColor diffuseColor(0);
				//RGBColor specularColor();
				while (std::getline(newFile, line))
				{
					//char firstChar = line.at(0);
					vector<string> tokens;
					if (line.compare("") == 0)
					{
						continue;
					}
					else
					{
						ParseStringSentence(line, " ", tokens);
					}

					if (line.at(0) == '#')
					{
						continue;
					}
					else if (tokens.size() > 0 && strcmp(tokens[0].c_str(), "newmtl") == 0)
					{
						if (!firstTime)
						{
							materialName = tokens[1];
							firstTime = true;
						}
						else
						{
							//first save then clear and store the new texture name

							bool solidColorExists = detailsArray[0].compare("") != 0;
							bool textureExists = detailsArray[1].compare("") != 0;
							
							Engine::Matte* currentMaterial;

							//only color
							if (solidColorExists && !textureExists)
							{
								vector<std::string> subTokens;

								ParseStringSentence(detailsArray[0], " ", subTokens);

								diffuseColor = RGBColor((float)atof(subTokens[0].c_str()), (float)atof(subTokens[1].c_str()), (float)atof(subTokens[2].c_str()));
								
								Engine::Matte* currentMaterial = new Engine::Matte(materialName, 0.75, diffuseColor);
								allMaterials[materialName] = currentMaterial;
								
							}
							else if (!solidColorExists && textureExists)
							{
								
								Engine::Matte* currentMaterial = new Engine::Matte(materialName, 0.75, detailsArray[1]);
								allMaterials[materialName] = currentMaterial;
							}
							else
							{
								vector<std::string> subTokens;

								ParseStringSentence(detailsArray[0], " ", subTokens);

								diffuseColor = RGBColor((float)atof(subTokens[0].c_str()), (float)atof(subTokens[1].c_str()), (float)atof(subTokens[2].c_str()));

								Engine::Matte* currentMaterial = new Engine::Matte(materialName, 0.75, detailsArray[1],diffuseColor);
								allMaterials[materialName] = currentMaterial;
							}

							detailsArray[0] = "";
							detailsArray[1] = "";

							materialName = tokens[1];

						}
						
					}
					else  if (tokens.size() > 0 && strcmp(tokens[0].c_str(), "Kd") == 0)
					{
						detailsArray[0] = tokens[1] + " " + tokens[2] + " " + tokens[3];
					}
					else if (tokens.size() > 0 && strcmp(tokens[0].c_str(), "map_Kd") == 0)
					{
						/*
							format is : map_Kd filename.ppm
							*/
						//name of file token [1]
						stringstream currentPath;

						vector<string> pathTokens;
						ParseStringSentence(path, "\\", pathTokens);

						for (int index = 0; index < pathTokens.size() - 1; index++)
						{
							if (index == 0)
							{
								currentPath << ".";
							}
							else
							{
								currentPath << "\\";
								currentPath << pathTokens[index];
							}

						}

						currentPath << "\\" << tokens[1];

						//fix here tonight
						detailsArray[1] = currentPath.str();
					}
				}
			}
			catch (exception e)
			{
				cout << "problem at line "<< line << endl;
			}
			newFile.close();
		}
		else
		{
			cout << "mtl file not found" << endl;
		}
	}



	vector<GeometricObjects*> ParseObjFile(string path, Point3D objectCenter = Point3D(0), bool usesTextures = false)
	{
		vector<Point3D> allVertices;
		vector<GeometricObjects*> allTriangles;
		vector<Point3D> allNormals;
		vector<Point3D> allTextureVertex;
		std::map<string, Engine::Material*> allMaterials;
		//updated 
		std::map<string, TriangleVertex> producedVertices;
		string emptyString = "";

		bool materialsAvailable = false;

		bool v = false;
		bool vt = false;
		bool vn = false;

		string mtlObjectName = "";

		char secondChar = '_';

		string line;
		std::ifstream myfile(path);
		if (myfile.is_open())
		{
			try
			{
				while (std::getline(myfile, line))
				{

					vector<string> tokens;

					string currentToken = "";
					ParseStringSentence(line, " ", tokens);
					
					if (line.compare(emptyString) == 0 || tokens.size() == 0)
					{
						continue;
					}

					char firstChar = line.at(0);
					if (line.length() > 1)
						secondChar = line.at(1);

					if (firstChar =='#')
					{
						continue;
					}

					//if materials mtllib 
					else if (firstChar == 'm' && secondChar == 't')
					{
						//load materials
						string materialPath = tokens[1];

						vector<string> pathTokens;
						ParseStringSentence(path, "\\", pathTokens);

						stringstream textFilePath;
						if (pathTokens.size() > 1)
						{
							for (int index = 0; index < pathTokens.size() - 1; index ++)
							{
								if (index == 0)
									textFilePath << ".\\" << pathTokens[index];
								else
									textFilePath << "\\" << pathTokens[index];
							}
						}


						textFilePath << "\\" << materialPath;
						GetMaterials(textFilePath.str(), allMaterials, usesTextures);
						materialsAvailable = true;
					}
					//when it finds usemtl 
					else if (firstChar == 'u' && secondChar == 's')
					{
						mtlObjectName = tokens[1];
					}
					else if (tokens.size() > 0)
					{
						//first token is always what is the type mof the object eg V or F
						string typeOfLine = tokens[0];
						if (firstChar == 'f')
						{
							//tokens = vector<string>(tokens.begin() + 1, tokens.end());

							try
							{
								//triangle
								if (tokens.size() == 4)
								{
									vector<string> output;

									TriangleVertex a;
									TriangleVertex b;
									TriangleVertex c;

									for (int subIndex = 1; subIndex < 4; subIndex++)
									{
										TriangleVertex& pointer = subIndex == 1 ? a : subIndex == 2 ? b : c;
										vector<std::string> output;

										ParseValues(tokens[subIndex], "/", output);

										std::map<string, TriangleVertex>::iterator it = producedVertices.find(tokens[subIndex]);

										if (it != producedVertices.end())
										{
											pointer = producedVertices[tokens[subIndex]];
										}
										else
										{
											Point3D tempVertex = output[0].compare("") == 0 ? NULL : allVertices.at((int)atoi(output[0].c_str()) - 1);
											Point3D temptextureVertex = output[1].compare("") == 0 ? NULL : allTextureVertex.at(atoi(output[1].c_str()) - 1);
											Point3D tempNormal = output[2].compare("") == 0 ? NULL : allNormals.at(atoi(output[2].c_str()) - 1);
											producedVertices[tokens[subIndex]] = TriangleVertex(tempVertex, tempNormal, temptextureVertex);;
											pointer = producedVertices[tokens[subIndex]];
										}
									}

									//cout << mtlObjectName << endl;
									Engine::Material *currentMaterial = allMaterials[mtlObjectName];

									if (currentMaterial != NULL)
									{
										Triangle *currentTriangle = new Triangle(a, b, c, currentMaterial, mtlObjectName);
										allTriangles.push_back(currentTriangle);
									}
									else
									{
										cout << "we may have a problem" << endl;
									}


									
								
								}
								else if (tokens.size() == 5)
								{
									vector<string> output;

									TriangleVertex vertexArray[4];

									for (int subIndex = 1; subIndex <= 4; subIndex++)
									{
										TriangleVertex& pointer = vertexArray[subIndex - 1];
										vector<std::string> output;

										ParseValues(tokens[subIndex], "/", output);

										std::map<string, TriangleVertex>::iterator it = producedVertices.find(tokens[subIndex]);

										if (it != producedVertices.end())
										{
											pointer = producedVertices[tokens[subIndex]];
										}
										else
										{
											Point3D tempVertex = output[0].compare("") == 0 ? NULL : allVertices.at((int)atoi(output[0].c_str()) - 1);
											Point3D temptextureVertex = output[1].compare("") == 0 ? NULL : allTextureVertex.at(atoi(output[1].c_str()) - 1);
											Point3D tempNormal = output[2].compare("") == 0 ? NULL : allNormals.at(atoi(output[2].c_str()) - 1);
											producedVertices[tokens[subIndex]] = TriangleVertex(tempVertex, tempNormal, temptextureVertex);;
											pointer = producedVertices[tokens[subIndex]];
										}
									}

									Engine::Quad * currentQuad;

									if (materialsAvailable)
										currentQuad = new Engine::Quad(vertexArray,allMaterials[mtlObjectName]);
									else
										currentQuad = new Engine::Quad(vertexArray);
									
									allTriangles.push_back(currentQuad);
								}
								
							}
							catch (std::exception e)
							{
								std::cout << "error " << line <<std::endl;
							}
						}
						else if (firstChar == 'v' && secondChar == 'n')
						{
							float xCoor = atof(tokens[1].c_str());
							float yCoor = atof(tokens[2].c_str());
							float zCoor = atof(tokens[3].c_str());

							Point3D currentVector(xCoor, yCoor, zCoor);
							allNormals.push_back(currentVector);
						}
						else if (firstChar == 'v' && secondChar == 't')
						{
							float xCoor = atof(tokens[1].c_str());
							float yCoor = atof(tokens[2].c_str());
							//float zCoor = atof(tokens[3].c_str());

							Point3D currentVector(xCoor, yCoor, 0);
							allTextureVertex.push_back(currentVector);
						}
						else if(firstChar == 'v')
						{
							float xCoor = atof(tokens[1].c_str());
							float yCoor = atof(tokens[2].c_str());
							float zCoor = atof(tokens[3].c_str());

							Point3D currentVector((xCoor + objectCenter.x), (yCoor + objectCenter.y), (zCoor + objectCenter.z));
							allVertices.push_back(currentVector);
						}

					}
					tokens.clear();
				}

				myfile.close();
			}
			catch (exception e)
			{
				cout << "Error when reading line" << endl;
			}
		}
		else
		{
			cout << "Error when opening the file"<<endl;
		}

		Engine::Logger::LogTrace("normals --- "+to_string(allNormals.size()));
		Engine::Logger::LogTrace("all vertices --- "+to_string(allVertices.size()));
		Engine::Logger::LogTrace("all materials --- " + to_string(allMaterials.size()));
		Engine::Logger::LogTrace("all triangles ---" + to_string(allTriangles.size()));


		return allTriangles;
	}

private:
};

#endif // !__OBJLOADER__
